¶

Машинное обучение. ВМК МГУ¶

Практическое задание 9: Кластеризация. Методы снижения размерности.¶

Важно! О формате сдачи¶

  • Практически все выделенные задания из ноутбука оцениваются по системе кросс-рецензирования. Задания, в которых надо реализовать свой код и послать в систему, выделены здесь и в PDF отдельно.
  • В этом задании также есть задания по типу unit-test.
  • При решении ноутбука используйте данный шаблон. Не нужно удалять текстовые ячейки с разметкой частей ноутбука и формулировками заданий. Добавлять свои ячейки, при необходимости, конечно, можно.
  • Везде, где в формулировке задания есть какой-либо вопрос (или просьба вывода), необходимо прописать ответ в ячейку (код или Markdown).
  • Наличие кода решения обязательно. Письменные ответы на вопросы без сопутствующего кода оцениваются в 0 баллов.
  • Если в ячейке написана фраза "Вывод"/"Ответ на вопрос" и.т.д., то ожидается ответ в виде текста (можете добавить ячейки с кодом, если считаете это необходимым, но это необязательно). Если в ячейке написано "Your code here" и пр. , то ожидается ответ в виде кода (можете добавить ячейки с кодом, если считаете это необходимым, но это необязательно). Если есть и ячейка с фразой "Вывод", и ячейка с фразой "Your code here", то в ответе ожидается и код, и текст

А также..

Если в ячейке написана фраза Ваш ответ здесь: (o・_・)ノ”(ノ_<、), то ожидается ответ в виде текста (можете добавить ячейки с кодом, если считаете это необходимым, но это необязательно). Если в ячейке написано # Ваш код здесь:\(º □ º l|l)/, то ожидается ответ в виде кода (можете добавить ячейки с кодом, если считаете это необходимым, но это необязательно). Если есть и ячейка с фразой Ваш ответ здесь: (o・_・)ノ”(ノ_<、), и ячейка с фразой # Ваш код здесь:\(º □ º l|l)/, то в ответе ожидается и код, и текст)

В этом задании вы..:

  • Познакомитесь с одним способом визуализации процесса обучения
  • Сравните между собой результаты разных способов кластеризации
  • Посмотрите и реализуете несколько метрик качества кластеризации
  • Попробуете разные методы снижения размерности

*Примерное время выполнения (execution time/время выполнения, если нажать run all) всех ячеек ноутбука (без ML-решения): 30 минут*</h3>


Перед началом выполнения переведите ноутбук в Доверенный режим (Trusted) для корректного отображения изображений:

In [ ]:
%config Completer.use_jedi = False
%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

0. Введение. [кросспроверка 2 балла]¶

0.1 О задании¶

В данной работе вам предстоит познакомится с методами машинного обучения без учителя — кластеризацией и алгоритмами снижения размерности.

В данном задании предлагается две основных части ($1, 2$) и одна вспомогательная ($0$). Сумарное всемя выполнения ноутбука порядка двух часов при выполнении в сервисах Kaggle/Google Colab. НЕ РЕКОМЕНДУЕТСЯ использовать Kaggle так как в нём корректно работают интерактивные визуализации.

0.2 Необходимые библиотеки¶

Установка в Google Colab¶

Если вы используете Google Colab, то вам достаточно установить библиотеки с помощью ячейки ниже.

После установки нужных версий нажмите на кнопку Restart runtime, которая появится в следующей ячейке после завершения ее выполнения.

После перезапуска (Restart runtime) устанавливать библиотеки еще раз не нужно, достаточно проверить, что установилась их новая версия.

Важно! Устанавливать нужные версии нужно каждый раз, когда создается новый рантайм. Например, если вы 2 часа подряд делаете это задание, то подготовить библиотеки достаточно 1 раз. Но если вы, например, начали в понедельник, затем закрыли/выключили ноутбук, то при продолжении в среду, вам нужно будет запустить рантайм заново и следовательно заново установить библиотеки.

**Внимание!** В этом задании мы будем использовать полное виртуальное окружение, так как понадобятся библиотеки torch и tensorflow

Обратите внимание, что установка torch и tensorflow через pip может сломать ваше окружение, особенно если вы используете GPU. Выполняйте их установку в соответствии с Вашей конфигурацией системы или в отдельном виртуальном окружении

In [ ]:
! pip install --upgrade gdown
! gdown 13tID3G5Bd5rKtmUd_s6SvSI_bG3zgmAS
! pip install -r ./requirements_2023_24.txt
Requirement already satisfied: gdown in /usr/local/lib/python3.10/dist-packages (5.1.0)
Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.10/dist-packages (from gdown) (4.12.3)
Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from gdown) (3.13.3)
Requirement already satisfied: requests[socks] in /usr/local/lib/python3.10/dist-packages (from gdown) (2.31.0)
Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from gdown) (4.66.1)
Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4->gdown) (2.5)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown) (3.6)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown) (2024.2.2)
Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown) (1.7.1)
Downloading...
From: https://drive.google.com/uc?id=13tID3G5Bd5rKtmUd_s6SvSI_bG3zgmAS
To: /content/requirements_2023_24.txt
100% 443/443 [00:00<00:00, 1.40MB/s]
Requirement already satisfied: catboost==1.2.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 1)) (1.2.1)
Requirement already satisfied: gdown==5.1.0 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 2)) (5.1.0)
Requirement already satisfied: h5py==3.9.0 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 3)) (3.9.0)
Requirement already satisfied: hyperopt==0.2.7 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 4)) (0.2.7)
Requirement already satisfied: ipympl==0.9.3 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 5)) (0.9.3)
Requirement already satisfied: ipywidgets==7.7.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 6)) (7.7.1)
Requirement already satisfied: jupyterlab==3.6.3 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 7)) (3.6.3)
Requirement already satisfied: keras==2.13.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 8)) (2.13.1)
Requirement already satisfied: lightgbm==4.0.0 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 9)) (4.0.0)
Requirement already satisfied: matplotlib==3.7.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 10)) (3.7.1)
Requirement already satisfied: matplotlib-inline==0.1.6 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 11)) (0.1.6)
Requirement already satisfied: numpy==1.23.5 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 12)) (1.23.5)
Requirement already satisfied: pandas==1.5.3 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 13)) (1.5.3)
Requirement already satisfied: pep8==1.7.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 14)) (1.7.1)
Requirement already satisfied: plotly==5.15.0 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 15)) (5.15.0)
Requirement already satisfied: pycodestyle==2.11.0 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 16)) (2.11.0)
Requirement already satisfied: pytest==7.4.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 17)) (7.4.1)
Requirement already satisfied: scikit-image==0.19.3 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 18)) (0.19.3)
Requirement already satisfied: scikit-learn==1.2.2 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 19)) (1.2.2)
Requirement already satisfied: scipy==1.11.2 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 20)) (1.11.2)
Requirement already satisfied: seaborn==0.12.2 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 21)) (0.12.2)
Requirement already satisfied: torch==2.0.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 22)) (2.0.1)
Requirement already satisfied: torchvision==0.15.2 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 23)) (0.15.2)
Requirement already satisfied: tqdm==4.66.1 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 24)) (4.66.1)
Requirement already satisfied: umap-learn==0.5.3 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 25)) (0.5.3)
Requirement already satisfied: xgboost==1.7.6 in /usr/local/lib/python3.10/dist-packages (from -r ./requirements_2023_24.txt (line 26)) (1.7.6)
Requirement already satisfied: graphviz in /usr/local/lib/python3.10/dist-packages (from catboost==1.2.1->-r ./requirements_2023_24.txt (line 1)) (0.20.3)
Requirement already satisfied: six in /usr/local/lib/python3.10/dist-packages (from catboost==1.2.1->-r ./requirements_2023_24.txt (line 1)) (1.16.0)
Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.10/dist-packages (from gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (4.12.3)
Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (3.13.3)
Requirement already satisfied: requests[socks] in /usr/local/lib/python3.10/dist-packages (from gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (2.31.0)
Requirement already satisfied: networkx>=2.2 in /usr/local/lib/python3.10/dist-packages (from hyperopt==0.2.7->-r ./requirements_2023_24.txt (line 4)) (3.2.1)
Requirement already satisfied: future in /usr/local/lib/python3.10/dist-packages (from hyperopt==0.2.7->-r ./requirements_2023_24.txt (line 4)) (0.18.3)
Requirement already satisfied: cloudpickle in /usr/local/lib/python3.10/dist-packages (from hyperopt==0.2.7->-r ./requirements_2023_24.txt (line 4)) (2.2.1)
Requirement already satisfied: py4j in /usr/local/lib/python3.10/dist-packages (from hyperopt==0.2.7->-r ./requirements_2023_24.txt (line 4)) (0.10.9.7)
Requirement already satisfied: ipython<9 in /usr/local/lib/python3.10/dist-packages (from ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (7.34.0)
Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.10/dist-packages (from ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.2.0)
Requirement already satisfied: pillow in /usr/local/lib/python3.10/dist-packages (from ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (9.4.0)
Requirement already satisfied: traitlets<6 in /usr/local/lib/python3.10/dist-packages (from ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (5.7.1)
Requirement already satisfied: ipykernel>=4.5.1 in /usr/local/lib/python3.10/dist-packages (from ipywidgets==7.7.1->-r ./requirements_2023_24.txt (line 6)) (5.5.6)
Requirement already satisfied: widgetsnbextension~=3.6.0 in /usr/local/lib/python3.10/dist-packages (from ipywidgets==7.7.1->-r ./requirements_2023_24.txt (line 6)) (3.6.6)
Requirement already satisfied: jupyterlab-widgets>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from ipywidgets==7.7.1->-r ./requirements_2023_24.txt (line 6)) (3.0.10)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (24.0)
Requirement already satisfied: tornado>=6.1.0 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (6.3.3)
Requirement already satisfied: jupyter-core in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (5.7.2)
Requirement already satisfied: jupyterlab-server~=2.19 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.25.4)
Requirement already satisfied: jupyter-server<3,>=1.16.0 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.24.0)
Requirement already satisfied: jupyter-ydoc~=0.2.3 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.2.5)
Requirement already satisfied: jupyter-server-ydoc~=0.8.0 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.8.0)
Requirement already satisfied: nbclassic in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.0.0)
Requirement already satisfied: notebook<7 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (6.5.5)
Requirement already satisfied: jinja2>=2.1 in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (3.1.3)
Requirement already satisfied: tomli in /usr/local/lib/python3.10/dist-packages (from jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.0.1)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib==3.7.1->-r ./requirements_2023_24.txt (line 10)) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib==3.7.1->-r ./requirements_2023_24.txt (line 10)) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib==3.7.1->-r ./requirements_2023_24.txt (line 10)) (4.50.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib==3.7.1->-r ./requirements_2023_24.txt (line 10)) (1.4.5)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib==3.7.1->-r ./requirements_2023_24.txt (line 10)) (3.1.2)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib==3.7.1->-r ./requirements_2023_24.txt (line 10)) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas==1.5.3->-r ./requirements_2023_24.txt (line 13)) (2023.4)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from plotly==5.15.0->-r ./requirements_2023_24.txt (line 15)) (8.2.3)
Requirement already satisfied: iniconfig in /usr/local/lib/python3.10/dist-packages (from pytest==7.4.1->-r ./requirements_2023_24.txt (line 17)) (2.0.0)
Requirement already satisfied: pluggy<2.0,>=0.12 in /usr/local/lib/python3.10/dist-packages (from pytest==7.4.1->-r ./requirements_2023_24.txt (line 17)) (1.4.0)
Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /usr/local/lib/python3.10/dist-packages (from pytest==7.4.1->-r ./requirements_2023_24.txt (line 17)) (1.2.0)
Requirement already satisfied: imageio>=2.4.1 in /usr/local/lib/python3.10/dist-packages (from scikit-image==0.19.3->-r ./requirements_2023_24.txt (line 18)) (2.31.6)
Requirement already satisfied: tifffile>=2019.7.26 in /usr/local/lib/python3.10/dist-packages (from scikit-image==0.19.3->-r ./requirements_2023_24.txt (line 18)) (2024.2.12)
Requirement already satisfied: PyWavelets>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-image==0.19.3->-r ./requirements_2023_24.txt (line 18)) (1.5.0)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn==1.2.2->-r ./requirements_2023_24.txt (line 19)) (1.3.2)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn==1.2.2->-r ./requirements_2023_24.txt (line 19)) (3.4.0)
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (4.10.0)
Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (1.12)
Requirement already satisfied: nvidia-cuda-nvrtc-cu11==11.7.99 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.7.99)
Requirement already satisfied: nvidia-cuda-runtime-cu11==11.7.99 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.7.99)
Requirement already satisfied: nvidia-cuda-cupti-cu11==11.7.101 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.7.101)
Requirement already satisfied: nvidia-cudnn-cu11==8.5.0.96 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (8.5.0.96)
Requirement already satisfied: nvidia-cublas-cu11==11.10.3.66 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.10.3.66)
Requirement already satisfied: nvidia-cufft-cu11==10.9.0.58 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (10.9.0.58)
Requirement already satisfied: nvidia-curand-cu11==10.2.10.91 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (10.2.10.91)
Requirement already satisfied: nvidia-cusolver-cu11==11.4.0.1 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.4.0.1)
Requirement already satisfied: nvidia-cusparse-cu11==11.7.4.91 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.7.4.91)
Requirement already satisfied: nvidia-nccl-cu11==2.14.3 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (2.14.3)
Requirement already satisfied: nvidia-nvtx-cu11==11.7.91 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (11.7.91)
Requirement already satisfied: triton==2.0.0 in /usr/local/lib/python3.10/dist-packages (from torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (2.0.0)
Requirement already satisfied: numba>=0.49 in /usr/local/lib/python3.10/dist-packages (from umap-learn==0.5.3->-r ./requirements_2023_24.txt (line 25)) (0.58.1)
Requirement already satisfied: pynndescent>=0.5 in /usr/local/lib/python3.10/dist-packages (from umap-learn==0.5.3->-r ./requirements_2023_24.txt (line 25)) (0.5.12)
Requirement already satisfied: setuptools in /usr/local/lib/python3.10/dist-packages (from nvidia-cublas-cu11==11.10.3.66->torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (67.7.2)
Requirement already satisfied: wheel in /usr/local/lib/python3.10/dist-packages (from nvidia-cublas-cu11==11.10.3.66->torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (0.43.0)
Requirement already satisfied: cmake in /usr/local/lib/python3.10/dist-packages (from triton==2.0.0->torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (3.27.9)
Requirement already satisfied: lit in /usr/local/lib/python3.10/dist-packages (from triton==2.0.0->torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (18.1.2)
Requirement already satisfied: jupyter-client in /usr/local/lib/python3.10/dist-packages (from ipykernel>=4.5.1->ipywidgets==7.7.1->-r ./requirements_2023_24.txt (line 6)) (6.1.12)
Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.19.1)
Requirement already satisfied: decorator in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (4.4.2)
Requirement already satisfied: pickleshare in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.7.5)
Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (3.0.43)
Requirement already satisfied: pygments in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (2.16.1)
Requirement already satisfied: backcall in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.2.0)
Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.10/dist-packages (from ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (4.9.0)
Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2>=2.1->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.1.5)
Requirement already satisfied: anyio<4,>=3.1.0 in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (3.7.1)
Requirement already satisfied: argon2-cffi in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (23.1.0)
Requirement already satisfied: nbconvert>=6.4.4 in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (6.5.4)
Requirement already satisfied: nbformat>=5.2.0 in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (5.10.3)
Requirement already satisfied: prometheus-client in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.20.0)
Requirement already satisfied: pyzmq>=17 in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (23.2.1)
Requirement already satisfied: Send2Trash in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.8.2)
Requirement already satisfied: terminado>=0.8.3 in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.18.1)
Requirement already satisfied: websocket-client in /usr/local/lib/python3.10/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.7.0)
Requirement already satisfied: platformdirs>=2.5 in /usr/local/lib/python3.10/dist-packages (from jupyter-core->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (4.2.0)
Requirement already satisfied: jupyter-server-fileid<1,>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.9.1)
Requirement already satisfied: ypy-websocket<0.9.0,>=0.8.2 in /usr/local/lib/python3.10/dist-packages (from jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.8.4)
Requirement already satisfied: y-py<0.7.0,>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from jupyter-ydoc~=0.2.3->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.6.2)
Requirement already satisfied: babel>=2.10 in /usr/local/lib/python3.10/dist-packages (from jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.14.0)
Requirement already satisfied: json5>=0.9.0 in /usr/local/lib/python3.10/dist-packages (from jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.9.24)
Requirement already satisfied: jsonschema>=4.18.0 in /usr/local/lib/python3.10/dist-packages (from jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (4.19.2)
Requirement already satisfied: nest-asyncio>=1.5 in /usr/local/lib/python3.10/dist-packages (from notebook<7->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.6.0)
Requirement already satisfied: notebook-shim>=0.2.3 in /usr/local/lib/python3.10/dist-packages (from nbclassic->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.2.4)
Requirement already satisfied: llvmlite<0.42,>=0.41.0dev0 in /usr/local/lib/python3.10/dist-packages (from numba>=0.49->umap-learn==0.5.3->-r ./requirements_2023_24.txt (line 25)) (0.41.1)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (3.6)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (2024.2.2)
Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4->gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (2.5)
Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /usr/local/lib/python3.10/dist-packages (from requests[socks]->gdown==5.1.0->-r ./requirements_2023_24.txt (line 2)) (1.7.1)
Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.10/dist-packages (from sympy->torch==2.0.1->-r ./requirements_2023_24.txt (line 22)) (1.3.0)
Requirement already satisfied: sniffio>=1.1 in /usr/local/lib/python3.10/dist-packages (from anyio<4,>=3.1.0->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.3.1)
Requirement already satisfied: parso<0.9.0,>=0.8.3 in /usr/local/lib/python3.10/dist-packages (from jedi>=0.16->ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.8.3)
Requirement already satisfied: attrs>=22.2.0 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (23.2.0)
Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2023.12.1)
Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.34.0)
Requirement already satisfied: rpds-py>=0.7.1 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.18.0)
Requirement already satisfied: jupyter-events>=0.5.0 in /usr/local/lib/python3.10/dist-packages (from jupyter-server-fileid<1,>=0.6.0->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.10.0)
Requirement already satisfied: lxml in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (4.9.4)
Requirement already satisfied: bleach in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (6.1.0)
Requirement already satisfied: defusedxml in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.7.1)
Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.4)
Requirement already satisfied: jupyterlab-pygments in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.3.0)
Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.8.4)
Requirement already satisfied: nbclient>=0.5.0 in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.10.0)
Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.5.1)
Requirement already satisfied: tinycss2 in /usr/local/lib/python3.10/dist-packages (from nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.2.1)
Requirement already satisfied: fastjsonschema in /usr/local/lib/python3.10/dist-packages (from nbformat>=5.2.0->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.19.1)
Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.10/dist-packages (from pexpect>4.3->ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.7.0)
Requirement already satisfied: wcwidth in /usr/local/lib/python3.10/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython<9->ipympl==0.9.3->-r ./requirements_2023_24.txt (line 5)) (0.2.13)
Requirement already satisfied: aiofiles<23,>=22.1.0 in /usr/local/lib/python3.10/dist-packages (from ypy-websocket<0.9.0,>=0.8.2->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (22.1.0)
Requirement already satisfied: aiosqlite<1,>=0.17.0 in /usr/local/lib/python3.10/dist-packages (from ypy-websocket<0.9.0,>=0.8.2->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.20.0)
Requirement already satisfied: argon2-cffi-bindings in /usr/local/lib/python3.10/dist-packages (from argon2-cffi->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (21.2.0)
Requirement already satisfied: python-json-logger>=2.0.4 in /usr/local/lib/python3.10/dist-packages (from jupyter-events>=0.5.0->jupyter-server-fileid<1,>=0.6.0->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.0.7)
Requirement already satisfied: pyyaml>=5.3 in /usr/local/lib/python3.10/dist-packages (from jupyter-events>=0.5.0->jupyter-server-fileid<1,>=0.6.0->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (6.0.1)
Requirement already satisfied: rfc3339-validator in /usr/local/lib/python3.10/dist-packages (from jupyter-events>=0.5.0->jupyter-server-fileid<1,>=0.6.0->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.1.4)
Requirement already satisfied: rfc3986-validator>=0.1.1 in /usr/local/lib/python3.10/dist-packages (from jupyter-events>=0.5.0->jupyter-server-fileid<1,>=0.6.0->jupyter-server-ydoc~=0.8.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.1.1)
Requirement already satisfied: cffi>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from argon2-cffi-bindings->argon2-cffi->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.16.0)
Requirement already satisfied: webencodings in /usr/local/lib/python3.10/dist-packages (from bleach->nbconvert>=6.4.4->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (0.5.1)
Requirement already satisfied: pycparser in /usr/local/lib/python3.10/dist-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->jupyter-server<3,>=1.16.0->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.22)
Requirement already satisfied: fqdn in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.5.1)
Requirement already satisfied: isoduration in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (20.11.0)
Requirement already satisfied: jsonpointer>1.13 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.4)
Requirement already satisfied: uri-template in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.3.0)
Requirement already satisfied: webcolors>=1.11 in /usr/local/lib/python3.10/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.13)
Requirement already satisfied: arrow>=0.15.0 in /usr/local/lib/python3.10/dist-packages (from isoduration->jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (1.3.0)
Requirement already satisfied: types-python-dateutil>=2.8.10 in /usr/local/lib/python3.10/dist-packages (from arrow>=0.15.0->isoduration->jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab==3.6.3->-r ./requirements_2023_24.txt (line 7)) (2.9.0.20240316)
In [ ]:
import catboost
assert(catboost.__version__ == '1.2.1')

Здесь перечислены основные функции и библиотеки, которые могут понадобиться Вам в процессе выполнения задания. Подключение других библиотек возможно, но нежелательно. Работа каких-либо других библиотек не гарантируется.

In [ ]:
import os

import gdown

import scipy

import numpy as np

import tqdm.auto as tqdm

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox

from ipywidgets import interactive, fixed, interact_manual, IntSlider, FloatLogSlider, FloatSlider

import torch
from torchvision.datasets import CIFAR10

# Необходима преварительная установка tensorflow
from keras.applications.inception_v3 import InceptionV3, preprocess_input

import sklearn

from sklearn.decomposition import KernelPCA
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering

# Библиотека umap-learn, а не umap
from umap import UMAP
from sklearn.manifold import TSNE, Isomap

from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification, make_moons, make_blobs
from sklearn.preprocessing import StandardScaler, MinMaxScaler

from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter("ignore")

0.3 Введение в интерактивные графики jupyter notebook [кросспроверка 2 балла]¶

Информация об особенностях интерактивных графиков вынесена в отдельный ноутбук Deep dive into .ipynb.ipynb. Ознакомьтесь с ним перед дальнейшим выполнением задания!

Задание 0.0 [кросспроверка, 2 балла][код, вопрос]¶

  • Поиграйтесь с интерактивными графиками.
  • Посмотрите в чём заключается смысл функций interactive, interact_manual, fixed, IntSlider, FloatLogSlider.
  • Какие ещё слайдеры и динамические виджеты бывают?
  • Как вам кажется, удобный ли это инструмент?
  • Предложите несколько примеров, когда он мог бы ещё пригодиться.
In [ ]:
import ipywidgets as widgets
from IPython.display import display

def multiply(a, b):
    print(f"{a} умножить на {b} равно {a * b}")

int_slider = widgets.IntSlider(value=1, min=0, max=10, step=1, description='Целое число:')
float_slider = widgets.FloatSlider(value=1.0, min=0, max=10.0, step=0.1, description='Дробное число:')

interactive_plot = widgets.interactive(multiply, a=int_slider, b=float_slider)

display(interactive_plot)
interactive(children=(IntSlider(value=1, description='Целое число:', max=10), FloatSlider(value=1.0, descripti…
In [ ]:
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact, FloatSlider

def plot_parabola(a=1, b=0):
    x = np.linspace(-10, 10, 400)
    y = a * x**2 + b * x
    plt.figure(figsize=(10, 6))
    plt.plot(x, y)
    plt.title('Парабола $y = ax^2 + bx$')
    plt.xlabel('x')
    plt.ylabel('y')
    plt.grid(True)
    plt.show()

a_slider = FloatSlider(value=1, min=0.1, max=2.0, step=0.1, description='Коэффициент a:')
b_slider = FloatSlider(value=0, min=-2.0, max=2.0, step=0.1, description='Коэффициент b:')

interact(plot_parabola, a=a_slider, b=b_slider)
interactive(children=(FloatSlider(value=1.0, description='Коэффициент a:', max=2.0, min=0.1), FloatSlider(valu…
Out[ ]:
plot_parabola
def plot_parabola(a=1, b=0)
/content/<ipython-input-96-f500761a48ea><no docstring>

*Ваш ответ здесь:* (o・・)ノ”(ノ<、)
Еще бывает FloatSlider, который позволяет выбирать дробные значения, Dropdown для выбора из списка, Checkbox для создания флажков, Button для кнопок

Эти инструменты могут быть очень удобными для исследования данных, тестирования гипотез в реальном времени и для обучения, так как они позволяют легко визуализировать изменения.

Эти виджеты можно использовать, например, для задания температуры у модели. Можно использоваться для демонстрация математических функций, где пользователи могут изменять коэффициенты и наблюдать за изменением графика функции.

Обратите внимание, что динамическое содержимое может некорректно сохраняться в jupyter notebook. Используйте его только для локальной визуализации. Все графики, необходимые для сдачи задания должны быть статическими. Переход в статический режим происходит с помощью магической команды %matplotlib inline.

Обязательно убедитесь, что все графики корректно сохранились корректно в ноутбуке перед сдачей в систему.

In [ ]:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 300

0.4 Ещё несколько важных замечаний¶

При выполнении задания запрещено:

  1. Менять те seed, которые явно указаны в коде
  2. Менять прототипы функций, классов, методов классов
  3. Менять константы, используемые для генерации выборок

При оформлении задания обратите внимание на форматирование кода и на оформление графиков:

  • Весь код должен быть оформлен в строгом соответствии с PEP8

Графики должны быть с одной стороны понятными и информативными, а с другой стороны красивыми. Вот несколько пунктов, которые помогут удовлетворить этим требования:

  1. Все графики должны быть отрисованы в векторном формате. Обратите внимание, что смена режима графиков с динамического на статический и обратно может приводить к сбросу параметров отрисовки графиков. Переход в векторный режим можно выполнить с помощью команды matplotlib_inline.backend_inline.set_matplotlib_formats('pdf', 'svg'). Если изображения в векторном формате приводят к слишком большому размеру Jupyter Notebook можете использовать растровые изображения с высоким dpi. Напирмер, можно установить глобальный dpi в matplotlib: matplotlib.rcParams['figure.dpi'] = 300
  2. На всех графиках без исключения должна быть нарисована сетка
  3. Все графики и группы графиков должны иметь заголовок (title)
  4. При необходимости оси должны быть подписаны
  5. Если на графике отображено несколько сущностей (линии/точки/bar разных цветов, формы и так далее), то необходима исчерпывающая легенда
  6. Все линии на графиках должны быть чётко видны (нет похожих цветов или цветов, сливающихся с фоном и так далее)
  7. Масштаб по каждой оси на графике должен быть выбран правильно. Используйте масштабы log, symlog по необходимости
  8. Если отображена величина, имеющая очевидный диапазон значений (например, проценты могут быть от 0 до 100), то желательно масштабировать ось на весь диапазон значений (исключением является случай, когда вам необходимо показать малое отличие, которое незаметно в таких масштабах)
  9. Частота отметок по каждой оси должна быть тщательно подобрана, по необходимости задавайте [xy]ticks, [xy]ticklabels вручную. Подписи тиков на осях не должны сливаться как на одной оси, так и между ними
  10. Помните, что matplotlib умеет выполнять рендеринг Latex. Используйте эту возможность для написания формул в заголовках, легенде и в подписях осей
  11. Используйте красивую цветовую палитру с хорошо различимыми цветами. Примеры цветовых палитр можно посмотреть здесь. При наличи особенностей восприятия цвета можно использовать специальные палитры:
    plt.style.use('seaborn-colorblind')
    # Или
    plt.style.use('tableau-colorblind10')
    # Затем, при отрисовке графиков не используйте параметр cmap
    
  12. Графики должны быть не супер-микро и не супер-макро по размерам, так, чтобы можно было увидеть все, что нужно

1. Кластеризация на синтетических данных. [кросспроверка 20 баллов, unittests 16 баллов]¶

В данной части мы рассмотрим несколько наборов синтетических данных и сравним как разные алгоритмы кластеризации ведут себя в низкоразмерных и высокоразмерных пространствах

a. Двумерные данные¶

Сгенерируем двумерные данные с $4$ естественными кластерами разного размера и плотности.

In [ ]:
data_moons, labels_moons = make_moons(n_samples=300, shuffle=True, noise=0.09, random_state=6417)
data_moons, labels_moons = data_moons[labels_moons == 0], labels_moons[labels_moons == 0]

data_blobs, labels_blobs = make_blobs(
    n_samples=500, n_features=2, centers=[(0, -0.2), (-1.5, -1.5), (-1.5, 1.5)],
    cluster_std=[0.2, 0.4, 0.6], center_box=[-10.0, 10.0],
    shuffle=True, random_state=6417, return_centers=False
)

data = np.concatenate([data_moons, data_blobs])
labels = np.concatenate([labels_moons, labels_blobs + 1])

Определим вспомогательную функцию для отрисовки двумерных кластеризованных данных. При выполенении задания желательно пользоваться этой функцией для визуализации. При необходимости можете менять сигнатуру и поведение функции как вам удобно, оставляя стиль отрисовки в целом неизменным.

In [ ]:
def plot_2d_data(data, labels, title='Исходные данные', cmap='tab20', ax=None):
    '''
    Отрисовка 2d scatter plot.
    :param np.ndarray data: 2d массив точек
    :param Union[list, np.ndarray] labels: список меток для каждой точки выборки
    :param str title: Заголовок графика
    :param str cmap: Цветовая палитра
    :param ax Optional[matplotlib.axes.Axes]: Оси для отрисовки графика.
        Если оси не заданы, то создаётся новая фигура и сразу же происходит её отрисовка
        Иначе, график добавляется на существуюущие оси. Отрисовки фигуры не происходит
    '''
    n_clusters = len(np.unique(labels))

    if ax is None:
        fig, ax = plt.subplots(1, 1, figsize=(10, 5))
    else:
        fig = None

    scatter = ax.scatter(
        data[:, 0], data[:, 1], c=labels,
        cmap=plt.get_cmap(cmap, n_clusters)
    )

    cbar = plt.colorbar(scatter, label='Номер кластера', ax=ax)
    cbar.set_ticks(np.min(labels) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
    cbar.set_ticklabels(np.unique(labels))

    ax.set_title(title)
    ax.grid(True)

    if fig is not None:
        fig.tight_layout()
        plt.show()
In [ ]:
plot_2d_data(data, labels, title='Исходные данные', cmap='tab20')

**Задание 1.a.1 [кросспроверка, 3 балла][код]** ¶

Запустите следующие алгоритмы кластеризации на данной выборке: KMeans, DBSCAN, AgglomerativeClustering. Визуально подберите наилучшие параметры для этих алгоритмов (n_clusters, eps, min_samples, linkage). Изобразите наилучшие получившиеся разбиения на графиках.

Советы:

  1. Можете использовать виджеты для ручного подбора параметров
  2. Используйте plot_2d_data для отрисовки графиков. Обязательно меняйте заголовок
  3. Помните, динамический контент может не сохраняться в ноутбуке. Поэтому после подбора оптимальных параметров нарисуйте соответствующий рисунок в статическом режиме
In [ ]:
# Ваш код здесь:\(º □ º l|l)/
%matplotlib inline
plt.style.use("ggplot")
matplotlib.rcParams['figure.dpi'] = 100
In [ ]:
def Kmeans(n_clusters):
  labels = KMeans(n_clusters=int(n_clusters)).fit_predict(data)
  plot_2d_data(data, labels, title='KMeans', cmap='tab20')


interactive_plot = interactive(Kmeans,
    n_clusters=FloatSlider(value=3, min=1, max=10, step=1, description=r'n_clusters'))
interactive_plot
interactive(children=(FloatSlider(value=3.0, description='n_clusters', max=10.0, min=1.0, step=1.0), Output())…
In [ ]:
Kmeans(3)
In [ ]:
def dbscan(eps, min_samples):
    labels_DBSCAN = DBSCAN(eps=eps, min_samples=int(min_samples)).fit_predict(data)
    plot_2d_data(data, labels_DBSCAN, title='DBSCAN', cmap='tab20')

interactive_plot = interactive(
    dbscan,
    eps=FloatSlider(value=0.4,
                    min=0.01, max=1.5, step=0.1, description=r'eps'),
    min_samples=FloatSlider(value=3,
                            min=1, max=10, step=1, description=r'min_samples')
)
interactive_plot
interactive(children=(FloatSlider(value=0.4, description='eps', max=1.5, min=0.01), FloatSlider(value=3.0, des…
In [156]:
dbscan(eps = 0.4, min_samples = 3)
In [ ]:
def AggClust(n_clusters):
  labels_AggClust = AgglomerativeClustering(n_clusters=int(n_clusters), linkage= "average").fit_predict(data)
  plot_2d_data(data, labels_AggClust, title='AgglomerativeClustering', cmap='tab20')


interactive_plot = interactive(AggClust,n_clusters=FloatSlider(value=3, min=1, max=10, step=1, description=r'n_clusters'))
interactive_plot
interactive(children=(FloatSlider(value=3.0, description='n_clusters', max=10.0, min=1.0, step=1.0), Output())…
In [ ]:
AggClust(n_clusters=3)

**Задание 1.a.2 [кросспроверка, 2 балла][вопрос]**¶

Основываясь на знании о работе данных алгоритмов, объясните, почему кластеры были сформированы таким образом. Какой алгоритм лучше всего работает на предложенных данных?

*Ваш ответ здесь:* (o・・)ノ”(ノ<、)

  • Лучше всего отработал KMeans. В данных имеются выбросы, среди всех методов только KMeans правильно классифицировал эти элементы. Однако даже он не смог восстановить изначальное разбиение, для этой задачи стоит использовать любой из двух других методов, но они требуют более точной настройки для достижениях нужного результата.

b. Многомерные данные. Снижение размерности¶

Теперь попробуем кластеризовать данные в высокоразмерном пространстве. Существенным отличием от двумерного случая является невозможность прямой визуальной оценки кластеризации.

Одним из наглядных способов оценки кластеризации является снижение размерности. В данной части вам предлагается использовать алгоритм TSNE для визуализации данных.

In [ ]:
data, labels = make_classification(
    n_samples=1000, n_features=200, n_informative=100,
    n_repeated=0, n_classes=5, n_clusters_per_class=2, weights=None,
    flip_y=0.01, class_sep=2.5, hypercube=True, shift=0.0, scale=1.0,
    shuffle=True, random_state=6417
)

**Задание 1.b.1 [кросспроверка, 2 балла][код, вопрос]**¶

Примените алгоритм TSNE для снижения размерности до двух. Учтите, что TSNE суть есть метрический алгоритм и существенно зависит от масштаба признаков — некорректный и различающийся масштаб признаков гарантированно приведёт к неинтерпретируемым результатам. Можете попробовать подобрать гиперпараметры алгоритма для получения лучшей визуализации.

Изобразите получившиеся низкоразмерные вектора. Получилось ли сохранить кластеры при переходе в низкоразмерное пространство?

In [ ]:
# Ваш код здесь:\(º □ º l|l)/
In [ ]:
scaler = MinMaxScaler().fit_transform(data)
In [ ]:
data_scaled = TSNE(n_components=2, early_exaggeration=10).fit_transform(scaler)

plot_2d_data(data_scaled, labels, title="Редукция к размерности 2 с масштабирование")
In [ ]:
data_scaled = TSNE(n_components=2, perplexity = 50, early_exaggeration=10).fit_transform(data)

plot_2d_data(data_scaled, labels, title="Редукция к размерности 2 без масштабирования")

*Ваш ответ здесь:* (o・・)ノ”(ノ<、) При переходе в низкоуровневое пространство сохранить кластеры получилось, но не так выраженно. Можно заметить, что масштабирование важно при снижении размерности.

**Задание 1.b.2 [кросспроверка, 3 балла][код]**¶

Примените алгоритмы кластеризации из предыдущего пункта к новым данным. Изобразите получившиеся кластеры в векторном пространстве, полученном с помощью TSNE. Не забудьте подобрать оптимальные параметры (те же, что и в пункте 1.a.1) для всех алгоритмов. Помните, что большинство алгоритмов кластеризации также являются метрическими и существенно зависят от масштаба признаков.

Замечание: Обратите внимание, что применять алгоритмы кластеризации нужно к высокоразмерным векторам, а низкоразмерные вектора необходимо использовать только для визуализации.

In [ ]:
# Ваш код здесь:\(º □ º l|l)/
In [ ]:
def Kmeans(n_clusters):
  labels = KMeans(n_clusters=int(n_clusters)).fit_predict(scaler)
  plot_2d_data(data_scaled, labels, title='KMeans', cmap='tab20')


interactive_plot = interactive(Kmeans, n_clusters=FloatSlider(value=3, min=1, max=10, step=1, description=r'n_clusters'))
%matplotlib inline
interactive_plot
interactive(children=(FloatSlider(value=3.0, description='n_clusters', max=10.0, min=1.0, step=1.0), Output())…
In [ ]:
Kmeans(n_clusters = 3)
In [ ]:
def dbscan(eps, min_samples):
    labels_DBSCAN = DBSCAN(eps=eps, min_samples=int(min_samples)).fit_predict(scaler)
    plot_2d_data(data_scaled, labels_DBSCAN, title=f"DBSCAN{eps}", cmap='tab20')

interactive_plot = interactive(dbscan,
    eps=FloatSlider(value=4, min=0, max=30, step=0.1, description=r'eps'),
    min_samples=FloatSlider(value=3, min=1, max=10, step=1, description=r'min_samples'))
interactive_plot
interactive(children=(FloatSlider(value=4.0, description='eps', max=30.0), FloatSlider(value=3.0, description=…
In [ ]:
dbscan(eps=4, min_samples=3)
In [ ]:
def AggClust(n_clusters):
  labels_AggClust = AgglomerativeClustering(n_clusters=int(n_clusters), linkage= "ward").fit_predict(scaler)
  plot_2d_data(data_scaled, labels_AggClust, title='AgglomerativeClustering', cmap='tab20')


interactive_plot = interactive(AggClust,
    n_clusters=FloatSlider(value=3, min=1, max=50, step=1, description=r'n_clusters'))
interactive_plot
interactive(children=(FloatSlider(value=3.0, description='n_clusters', max=50.0, min=1.0, step=1.0), Output())…
In [ ]:
AggClust(n_clusters = 3)

**Задание 1.b.3 [кросспроверка, 2 балла][вопрос]**¶

Опишите получившиеся результаты. Все ли алгоритмы одинаково хорошо разделяют данные? Если какой-то алгоритм не справился с задачей, то предположите почему.

*Ваш ответ здесь:* (o・・)ノ”(ノ<、) Лучше всего справился KMeans (опять), причина может быть в выпуклости данных. Хуже всего справился DBSCAN.

c. Методы оценки кластеризации. Внутренние и внешние метрики.¶

Визуальная оценка кластеризации при работе с высокоразмерными данными затруднительна, так как существенно зависит от выбранного метода снижения размерности, который может плохо работать на конкретных данных. С другой стороны, визуальная оценка — субъективна. Поэтому необходимы численные оценки качества кластеризации.

В данном разделе вам нужно будет реализовать две метрики кластеризации и проверить их на практике.

Существует два основных подхода к оценке кластеризации — внутренние и внешние метрики. Первые используют только информацию о векторах-признаках объектов и метки кластеров, полученные из алгоритма кластеризации. Внешние же, используют информацию об истинной разметке объектов.

Silhouette¶

Метрика силуэт является классическим представителем внутренних метрик кластеризации. Её суть заключается в оценке двух параметров, характеризующих выделенные кластеры — компактность и отделимость.

Положим, что $C_{i}$ — номер кластера для объекта $i$.

$s_{i}$ — компактность кластеризации объекта $i$ определяется как среднее расстояние от него до всех объектов того же кластера: $$s_{i} = \frac{1}{|\{j : C_{j} = C_{i}\}| - 1} \sum\limits_{j : C_{j} = C_{i}} || x_{i} - x_{j} ||$$

$d_{i}$ — отделимость кластеризации объекта $i$ определяется как среднее расстояние от него до всех объектов второго по близости кластера: $$ d_{i} = \min_{C: C \neq C_{i}} \frac{1}{|\{j : C_{j} = C\}|} \sum\limits_{j : C_{j} = C} || x_{i} - x_{j} || $$

Тогда силуэт объекта $i$: $$\text{sil}_{i} = \frac{d_{i} - s_{i}}{\max(d_{i},s_{i})}$$

И, наконец, коэффициент силуэта для выборки определяется как среднее силуэтов объектов: $$S = \frac{1}{|X|}\sum\limits_{i} \text{sil}_{i}$$

Если кластер состоит из одного объекта, то его силуэт равен нулю.

**Задание 1.с.1 [unittests, 8 баллов]**¶

Реализуйте вычисление коэффициента силуэта для заданного разбиения. Подробная спецификация и описание входных данных к тестам указаны в описании задания.

При реализации обратите внимание на следующие пункты:

  1. При вычислении не должно возникать warning, бесконечностей и nan-ов
  2. Используйте не более одного цикла
  3. Учтите, что метки кластеров могут идти не по порядку и принимать произвольные значения
  4. Если в данных присутствует один кластер, то считайте что силуэт равен 0
  5. Если $s_{i} = d_{i} = 0 \Longrightarrow \text{sil}_{i} = 0$
  6. Разрешено использовать sklearn.metrics.pairwise_distances и аналоги
  7. Запрещено использовать любые библиотечные реализации коэффициента силуэта

Входные данные тестов удовлетворяют одному из следующих ограничений:

  1. Число объектов $n \le 3000$, размерность пространства $d \le 1200$
  2. Число объектов $n \le 5000$, размерность пространства $d = 1$

Несколько важных замечаний:

Замечание: Запрещается пользоваться библиотеками, импорт которых не объявлен в файле с шаблонами функций.

Замечание: Задания, в которых есть решения, содержащие в каком-либо виде взлом тестов, дополнительные импорты и прочие нечестные приемы, будут автоматически оценены в $0$ баллов без права пересдачи задания.

Замечание: Под циклами далее подразумеваются как явные Python-циклы (for, while, list comprehension, ...), так и неявное использование таких циклов внутри библиотек (np.apply_along_axis и подобные). В случае возникновения ошибки Time limit проверьте код на соответствие числа используемых циклов с требованиями к реализации.

Замечание: Для самопроверки доступны как публичные тесты (смотрите описание задания), так и тесты внутри Jupyter Notebook (смотрите тесты для Силуэта).

In [160]:
def silhouette_score(X, labels):

    distances = sklearn.metrics.pairwise_distances(X)
    num_clusters, count_clusters = np.unique(labels, return_counts=True)
    len_labels = len(labels)

    if len(num_clusters) <= 1:
        return 0

    masks = np.zeros((len_labels, len(num_clusters))).astype(bool)
    sum_dists = np.zeros((len_labels, len(num_clusters)))
    sizes_clusters = np.zeros(len_labels)

    for i, cluster in enumerate(num_clusters):
        masks[:, i] = labels == cluster
        sum_dists[:, i] = np.sum(distances[:, labels == cluster], axis=1)
        sizes_clusters[labels == cluster] = np.sum(labels == cluster)

    one_elem_cl = sizes_clusters == 1
    s = sum_dists[masks]
    s[one_elem_cl] = 0
    s[~one_elem_cl] /= (sizes_clusters[~one_elem_cl] - 1)

    d = np.min(((sum_dists / count_clusters)[~masks]).reshape(len_labels, -1), axis=1)
    d[one_elem_cl] = 0

    maxs = np.maximum(s, d)
    ans = np.divide(d - s, maxs, where=(maxs != 0))
    return np.mean(ans)

**Задание 1.c.2 [кросспроверка, 4 балла][код, вопрос]**¶

Посчитайте аналитически коэффициенты силуэта для объектов из примера ниже. Посчитайте силуэт аналитически и сравните его с выводом Вашей функции silhouette_score.

Замечание: Приведите подробные выкладки с использованием $\LaTeX$ (включая значения $s_{i}, d_{i}$). Используйте те же обозначения, что и в теоретической справке выше. Итоговый ответ для силуэта запишите с точностью три знака после запятой.

$x_1([2,2]),x_2([0,0]) - 1$ класс

$x_3([0,1]),x_4([1,0]) - 2$ класс

$s_1=\sqrt{8}, \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ d_1=\frac{1}{2}\cdot(\sqrt{5}+\sqrt{5})=\sqrt{5}, \ \ \ \ sil_1= - 0.21$

$s_2=\sqrt{8}, \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ d_2=\frac{1}{2}\cdot(\sqrt{1}+\sqrt{1})= 1, \ \ \ \ \ \ \ sil_2= - 0.64$

$s_3=\sqrt{1}+1 = 1.41, \ \ \ \ \ d_3=\frac{1}{2}\cdot(\sqrt{5}+\sqrt{1})=1.62, \ \ sil_3=0.13$

$s_4=\sqrt{1}+1 = 1.41, \ \ \ \ \ d_4=\frac{1}{2}\cdot(\sqrt{1}+\sqrt{5})=1.62, \ \ sil_4=0.13$

$silhouette\_score = \frac{-0.21 - 0.64 + 0.13 + 0.134}{4}\approx - 0.15$

In [161]:
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
ax.scatter([0, 2], [0, 2], s=100, c='r', label='Первый класс')
ax.scatter([0, 1], [1, 0], s=100, c='b', label='Второй класс')

ax.annotate('1', (0.1, 0.1))
ax.annotate('2', (0.1, 1.1))
ax.annotate('3', (1.1, 0.1))
ax.annotate('4', (1.9, 1.9))

ax.set_title('Выборка к заданию')
ax.grid(True)
ax.legend()

fig.tight_layout()
plt.show()

*Ваш ответ здесь:* (o・・)ノ”(ノ<、) Вычисления совпали

In [162]:
# Ваш код здесь:\(º □ º l|l)/
data, labels = np.array([[2, 2], [0, 0], [0, 1], [1, 0]]), np.array([0, 0, 1, 1])
round(silhouette_score(data, labels), 2)
Out[162]:
-0.15

Убедитесь, что Ваша реализация проходит минимальные тесты:

In [163]:
assert np.allclose(
    silhouette_score(
        np.array([[0, 0.], [0, 1], [1, 0], [2, 2]]), np.array([1, 0, 0, 1])
    ),
    np.mean([-0.64644661,  0.12596795,  0.12596795, -0.20943058])
)

assert np.allclose(
    silhouette_score(
        np.array([[0, 0.], [0, 1], [1, 0], [2, 2], [1, 1], [2, 0]]), np.array([1, 0, 0, 1, 2, 2])
    ),
    np.mean([-0.64644661,  0.12596795, -0.29289322, -0.39644661, -0.29289322, 0.12596795])
)

B-Cubed¶

Пусть существует разметка $(y_1, ... , y_l)$, не участвующая в обучении. Мы не использовали эту разметку в качестве дополнительного признака, так как нам не хочется мотивировать модель данным признаком. Тогда предлагается ввести оценку качества алгоритма кластеризации при помощи внешней разметки, саму же разметку тогда называют gold standard.

Один из вариантов учесть gold standard разметку — внешняя метрика B-Cubed. Данная метрика позволяет определять следующие особенности кластеризации:

  1. Гомогенность. Базовое свойство разделения разных объектов в разные кластеры:

  1. Полнота. Один кластер не должен дробиться на несколько маленьких:

  1. Rag-bag. Весь мусор должен быть в одном "мусорном"кластере, чтобы остальные кластеры были "чистыми":

  1. Cluster size vs. quantity. Лучше испортить один кластер с целью улучшить качество множества других:

Пусть $L(x)$ — gold standard, $C(x)$ — номер кластера, выдаваемый рассматриваемым алгоритмом.

Рассмотрим несколько величин: $$ \text{Correctness}(x, x^{\prime}) = \begin{cases} 1 , C(x) = C(x^{\prime}) ∧ L(x) = L(x^{\prime})\\ 0 , иначе \end{cases} $$ $$ \text{Precision-BCubed} = \underset{x}{\text{Avg}} \underset{x^{\prime}:C(x)=C(x^{\prime})}{\text{Avg}} \text{Correctness}(x, x^{\prime}) $$ $$ \text{Recall-BCubed} = \underset{x}{\text{Avg}} \underset{x^{\prime}:L(x)=L(x^{\prime})}{\text{Avg}} \text{Correctness}(x, x^{\prime}) $$

Тогда, $$ \text{B-Cubed} = F_{1} = 2\frac{\text{Precision-BCubed}\times\text{Recall-BCubed}}{\text{Precision-BCubed}+\text{Recall-BCubed}} $$

**Задание 1.с.3 [unittests, 8 баллов]**¶

Реализуйте вычисление метрики B-Cubed. Подробная спецификация и описание входных данных к тестам указаны в описании задания.

При реализации обратите внимание на следующие пункты:

  1. При вычислении не должно возникать warning, бесконечностей и nan-ов.
  2. Использование циклов запрещено
  3. Обратите внимание на параметр where у функций-агрегаторов в numpy ($numpy \geq 1.20.0$).
  4. Запрещено использовать любые библиотечные реализации B-Cubed.

Входные данные тестов удовлетворяют одному из следующих ограничений:

  1. Число объектов n ⩽ 1000, число подтестов в одном тесте T ⩽ 70

Несколько важных замечаний:

Замечание: Запрещается пользоваться библиотеками, импорт которых не объявлен в файле с шаблонами функций.

Замечание: Задания, в которых есть решения, содержащие в каком-либо виде взлом тестов, дополнительные импорты и прочие нечестные приемы, будут автоматически оценены в $0$ баллов без права пересдачи задания.

Замечание: Под циклами далее подразумеваются как явные Python-циклы (for, while, list comprehension, ...), так и неявное использование таких циклов внутри библиотек (np.apply_along_axis и подобные). В случае возникновения ошибки Time limit проверьте код на соответствие числа используемых циклов с требованиями к реализации.

Замечание: Для самопроверки доступны как публичные тесты (смотрите описание задания), так и тесты внутри Jupyter Notebook (смотрите тесты для B-Cubed).

In [164]:
def bcubed_score(labels_true, labels_pred):
    mask_same_cluster = labels_pred[:, None] == labels_pred[None, :]
    mask_same_label = labels_true[:, None] == labels_true[None, :]

    correctness = np.where(mask_same_cluster & mask_same_label, 1, 0)

    precision = np.sum(correctness, axis=1) / np.sum(mask_same_cluster, axis=1)
    precision_bcubed = np.mean(precision)

    recall = np.sum(correctness, axis=1) / np.sum(mask_same_label, axis=1)
    recall_bcubed = np.mean(recall)

    score = 2 * precision_bcubed * recall_bcubed / (precision_bcubed + recall_bcubed)

    return score

Убедитесь, что Ваша реализация проходит минимальные тесты:

In [165]:
assert np.allclose(bcubed_score(np.array([1]), np.array([1])), 1.0)
assert np.allclose(bcubed_score(np.array([1]), np.array([2])), 1.0)
assert np.allclose(bcubed_score(np.array([1, 2]), np.array([1, 1])), 2. / 3)
assert np.allclose(bcubed_score(np.array([1, 2]), np.array([1, 2])), 1.0)
assert np.allclose(bcubed_score(np.array([1, 2]), np.array([43, 12])), 1.0)
assert np.allclose(bcubed_score(np.array([1, 1, 2, 2]), np.array([1, 1, 1, 2])), 12. / 17)
assert np.allclose(bcubed_score(np.array([1, 2, 3, 4, 5]), np.array([1, 1, 1, 2, 2])), 4. / 7)

**Задание 1.с.4 [кросспроверка, 4 балла][код, вопрос]** ¶

  • Для каждого из трёх алгоритмов кластеризации переберите основные параметры (n_clusters, eps, min_samples) и замерьте качество кластеризации многомерных данных с помощью коэффициента силуэта и метрики B-Cubed.
  • Для каждого из алгоритмов на одной фигуре изобразите четыре графика — в первой строке два графика с зависимостью коэффициента силуэта и метрики B-Cubed от варьируемого параметра. Во второй строке — визуализация кластеризации с оптимальным параметром, выбранным с помощью первой и второй метрики (можете использовать plot_2d_data с параметром ax). Для DBSCAN перебирайте оба параметра одновременно и изобразите 2d-heatmap для каждой из метрик. Можете использовать вспомогательные функции для создания heatmap.
  • Совпали ли оптимальные параметры, определённые по этим метрикам? Совпали ли они с теми параметрами которые вы подобрали в предыдущем задании? Предположите почему они совпали/не совпали.
  • Можно ли использовать данные метрики для оценки кластеризации между разными алгоритмами? Какая из них лучше отражает качество?
In [166]:
data, labels = make_classification(
    n_samples=1000, n_features=200, n_informative=100,
    n_repeated=0, n_classes=5, n_clusters_per_class=2, weights=None,
    flip_y=0.01, class_sep=2.5, hypercube=True, shift=0.0, scale=1.0,
    shuffle=True, random_state=6417
)
In [167]:
n_clusters = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 30]
eps = [3, 3.4, 3.8, 4.2, 4.6, 5]
min_samples = [1, 5, 10, 15, 20, 40]

data = StandardScaler().fit_transform(data)

X = TSNE(n_components=2, early_exaggeration=10).fit_transform(data)
In [168]:
def KMeans_metrics(data, X, labels):
  data = StandardScaler().fit_transform(data)
  bcubed_metric, silhouette_metric = [], []
  n_clusters = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 30]

  for cl in n_clusters:
    sample = KMeans(n_clusters=cl).fit_predict(data)
    silhouette_metric.append(silhouette_score(data, sample))
    bcubed_metric.append(bcubed_score(labels, sample))


  best_sample_KMeans_s = KMeans(n_clusters=n_clusters[np.argmax(silhouette_metric)]).fit_predict(data)
  best_sample_KMeans_b = KMeans(n_clusters=n_clusters[np.argmax(bcubed_metric)]).fit_predict(data)

  fig, ax = plt.subplots(2, 2, figsize=(10, 5))
  fig.tight_layout()
  ax[0, 0].plot(n_clusters, silhouette_metric)
  ax[0, 0].set_xlabel("Number of clusters")
  ax[0, 0].set_title("Silhouette metric")
  ax[0, 1].set_title("Bcubed metric")
  ax[0, 1].set_xlabel("Number of clusters")
  ax[0, 1].plot(n_clusters, bcubed_metric)
  plot_2d_data(X, best_sample_KMeans_s, ax=ax[1, 0], title=" ")
  plot_2d_data(X, best_sample_KMeans_b, ax=ax[1, 1], title=" ")
In [ ]:
KMeans_metrics(data, X, labels)
In [169]:
def plot_heatmap(data, labels_x, labels_y, ax, bar_text):
    data = np.array(data)
    im = ax.imshow(data)
    ax.set_xticks(np.arange(data.shape[1]), labels=labels_x)
    ax.set_yticks(np.arange(data.shape[0]), labels=labels_y)
    ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)
    cbar = ax.figure.colorbar(im, ax=ax)
    cbar.ax.set_ylabel(bar_text, va="bottom")
    plt.setp(ax.get_xticklabels(), rotation=-90, ha="right")
In [170]:
def DBSCAN_metrics(data, X, labels):
  eps = np.linspace(start=13, stop=18, num=20)
  min_samples = [n for n in range(1, 21)]

  sil_scores = []
  bcubed_scores = []
  for i, ms in enumerate(min_samples):
      sil_scores.append([])
      bcubed_scores.append([])
      for j in eps:
          sil_scores[i].append(silhouette_score(data, DBSCAN(eps=j, min_samples=ms).fit_predict(data)))
          bcubed_scores[i].append(bcubed_score(labels, DBSCAN(eps=j, min_samples=ms).fit_predict(data)))

  fig, ax = plt.subplots(2, 2, figsize=(10, 5))
  plot_heatmap(sil_scores, eps, min_samples, ax[0, 0], 'silhouette_score')
  plot_heatmap(bcubed_scores, eps, min_samples, ax[0, 1], 'bcubed_score')
  plot_2d_data(X, DBSCAN(eps=18, min_samples=10).fit_predict(data), title='Оптимальная кластеризация по silhouette_score', ax=ax[1,0])
  plot_2d_data(X, DBSCAN(eps=17.21, min_samples=1).fit_predict(data), title='Оптимальная кластеризация по bcubed_score', ax=ax[1,1])
  plt.show()
In [171]:
DBSCAN_metrics(data, X, labels)
In [ ]:
def AgglClust_metrics(data, X, labels):
  bcubed_metric, silhouette_metric = [], []
  n_clusters = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 30]
  data = MinMaxScaler().fit_transform(data)

  for cl in n_clusters:
    sample = AgglomerativeClustering(n_clusters=cl).fit_predict(data)
    silhouette_metric.append(silhouette_score(data, sample))
    bcubed_metric.append(bcubed_score(labels, sample))


  best_sample_agg_s = AgglomerativeClustering(n_clusters=n_clusters[np.argmax(silhouette_metric)]).fit_predict(data)
  best_sample_agg_b = AgglomerativeClustering(n_clusters=n_clusters[np.argmax(bcubed_metric)]).fit_predict(data)

  fig, ax = plt.subplots(2, 2, figsize=(10, 5))
  fig.tight_layout()
  ax[0, 0].plot(n_clusters, silhouette_metric)
  ax[0, 1].plot(n_clusters, bcubed_metric)
  ax[0, 0].set_ylabel("Silhouette metric")
  ax[0, 1].set_ylabel("Bcubed metric")
  plot_2d_data(X, best_sample_agg_s, ax=ax[1, 0], title=" ")
  plot_2d_data(X, best_sample_agg_b, ax=ax[1, 1], title=" ")
  ax[0, 0].set_xlabel("Number of clusters")
  ax[0, 1].set_xlabel("Number of clusters");
In [ ]:
AgglClust_metrics(data,X,labels)

*Ваш ответ здесь:* (o・・)ノ”(ノ<、)

  • Для KMeans метрики совпали между собой, но не со значениями, подобранными визуально. DBSCAN снова плохо работает. Для AgglomerativeClustering никакие значения не совпали между собой.

    Кластеры выпуклые, KMeans работает отлично. AgglomerativeClustering стироит иерархию и из-за близости классов может ошибаться. Нужна более тонкая настройка гиперпараметров

  • Использовать методы для оценка разных алгоритмов нельзя, так как у алгоритмов разные приоритеты для межкластерного и внутрикластерного расстояния, как и у этих метрик. Лучше отражает качество метрика BCubed, так как она учитывает правильные метки


2. Кластеризация "естественных" данных. [кросспроверка 22 балла]¶

Синтетические данные имеют достаточно простую структуру, поэтому методы снижения размерности позволяют получать хорошее низкоразмерное представление с достаточно выраженными кластерами. Однако, реальные данные могут быть устроены существенно сложнее. Посмотрим как поведут себя методы снижения размерности на датасете с картинками CIFAR10.

Загрузим датасет. Будем использовать только часть обучающей выборки, чтобы ускорить вычисления на высокоразмерных данных.

In [172]:
cifar10_test_dataset = CIFAR10('./cifar10', train=False, download=True)
cifar10_train_dataset = CIFAR10('./cifar10', train=True, download=False)

cifar10_labels_test = np.array(cifar10_test_dataset.targets)
cifar10_labels_train = np.array(cifar10_train_dataset.targets)

cifar10_images_test = cifar10_test_dataset.data
cifar10_images_train = cifar10_train_dataset.data

cifar10_images_train, _, cifar10_labels_train, _ = train_test_split(
    cifar10_images_train, cifar10_labels_train,
    train_size=cifar10_images_test.shape[0], stratify=cifar10_labels_train, random_state=6886
)

cifar10_data_test = (cifar10_images_test.astype(np.float32) / 255.0).reshape([cifar10_images_test.shape[0], -1])
cifar10_data_train = (cifar10_images_train.astype(np.float32) / 255.0).reshape([cifar10_images_train.shape[0], -1])
Files already downloaded and verified

Отобразим данные в проекции на две случайные оси. Для удобства воспользуемся здесь ещё одним вариантом динамического контента в jupyter notebook — при наведении на точку на графике будем отображать исходную картинку.

In [173]:
def plot_interactive(lowd_data, images, labels, names, n_dots=1000, image_scale=1.0):
    with matplotlib.rc_context(rc={
        'font.size': image_scale * matplotlib.rcParams['font.size'],
        'xtick.major.size': image_scale * matplotlib.rcParams['xtick.major.size'],
        'xtick.minor.size': image_scale * matplotlib.rcParams['xtick.minor.size'],
        'ytick.major.size': image_scale * matplotlib.rcParams['ytick.major.size'],
        'ytick.minor.size': image_scale * matplotlib.rcParams['ytick.minor.size'],

        'axes.linewidth': image_scale * matplotlib.rcParams['axes.linewidth'],
        'grid.linewidth': image_scale * matplotlib.rcParams['grid.linewidth'],
        'patch.linewidth': image_scale * matplotlib.rcParams['patch.linewidth'],
        'xtick.major.width': image_scale * matplotlib.rcParams['xtick.major.width'],
        'xtick.minor.width': image_scale * matplotlib.rcParams['xtick.minor.width'],
        'ytick.major.width': image_scale * matplotlib.rcParams['ytick.major.width'],
        'ytick.minor.width': image_scale * matplotlib.rcParams['ytick.minor.width'],

        'lines.markeredgewidth': image_scale * matplotlib.rcParams['lines.markeredgewidth'],
    }):
        fig, ax = plt.subplots(1, 1, figsize=(image_scale * 10, image_scale * 5))
        fig.set_dpi(300)
        ax.grid(True)

        n_clusters = len(np.unique(labels))

        scatter = plt.scatter(
            lowd_data[:n_dots, 0], lowd_data[:n_dots, 1], s=image_scale * 10,
            c=labels[:n_dots], cmap=plt.get_cmap('tab20', n_clusters), edgecolors='none'
        )

        cbar = plt.colorbar(scatter, ax=ax, label='Название кластера')
        cbar.set_ticks(np.min(labels[:n_dots]) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
        cbar.set_ticklabels(names)

        offset_image = OffsetImage(images[0], zoom=image_scale * 2.0)
        ann_bbox = AnnotationBbox(
            offset_image, (0,0), xybox=(image_scale * 50., image_scale * 50.), xycoords='data',
            boxcoords="offset points", pad=0.3, arrowprops=dict(
                arrowstyle='->, head_length={0:.2f}, head_width={1:.2f}'.format(
                    image_scale * 0.4, image_scale * 0.2
                )
            )
        )
        ax.add_artist(ann_bbox)
        ax.set_title('Распределение данных CIFAR10 в проекции на 2 случайные оси')
        ann_bbox.set_visible(False)

        def image_hover(event):
            if scatter.contains(event)[0]:
                ind, *_ = scatter.contains(event)[1]["ind"]
                w, h = fig.get_size_inches() * fig.dpi
                ws = (event.x > w / 2.) * -1 + (event.x <= w / 2.)
                hs = (event.y > h / 2.) * -1 + (event.y <= h / 2.)
                ann_bbox.xybox = (image_scale * 50.0 * ws, image_scale * 50.0 * hs)
                ann_bbox.set_visible(True)
                ann_bbox.xy =(lowd_data[ind, 0], lowd_data[ind, 1])
                offset_image.set_data(images[ind])
            else:
                ann_bbox.set_visible(False)
            fig.canvas.draw_idle()

        fig.canvas.mpl_connect('motion_notify_event', image_hover)

        plt.show()
In [174]:
%matplotlib ipympl
matplotlib.rcParams['figure.dpi'] = 300

# Для работы в Google Colab нужно выполнить специфичную магию
# Обычно, она не срабатывает с первого раза, поэтому может потребоваться
#   несколько раз выполнить ячейку и несколько раз попробовать нарисовать график
try:
    from google.colab import output
    output.enable_custom_widget_manager()
except:
    pass
In [175]:
# Если картинка окажется слишком маленькой/большой, то поменяйте image_scale на подходящее значение
plot_interactive(
    cifar10_data_train[:, [17, 64]], cifar10_images_train, cifar10_labels_train,
    cifar10_test_dataset.classes, n_dots=2000, image_scale=0.35
)
Figure

Вернёмся в статичный режим отрисовки изображений:

In [176]:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 100
plt.style.use("ggplot")

Задание 2.1 [кросспроверка, 2 балла][код]

</font> Воспользуйтесь алгоритмами снижения размерности TSNE, UMAP, Isomap, KernelPCA для визуализации картинок.

Постройте визуализацию низкоразмерного представления, полученного с помощью этих моделей — изобразите четыре графика в одной строке. Во второй строке отобразите результат применения обученных моделей на тестовой выборке. Если для данного алгоритма невозможно сделать предсказания на тестовой выборке — оставьте соответствующий график пустым. Обозначьте разными цветами разные классы объектов. Для повышения производительности можете отобразить только часть выборки на графике ($1000\text{-}2000$ объектов).

Замечание: обратите внимание, что все алгоритмы снижения размерности также требуют правильного масштабирования признаков, для корректной работы и интерпретируемых результатов.

In [ ]:
# Ваш код здесь:\(º □ º l|l)/
def plot_grid(X_train, lab_train, X_test, lab_test):

  X_test_cut = StandardScaler().fit_transform(X_test)
  X_train_cut = StandardScaler().fit_transform(X_train)


  UMAP_model = UMAP(n_neighbors=15, n_components=2, min_dist=0.0)
  UMAP_model.fit(X_train_cut)
  X_UMAP_test = UMAP_model.transform(X_test_cut)
  X_UMAP_train = UMAP_model.transform(X_train_cut)

  TSNE_model = TSNE(n_components=2,
                    perplexity=30.0,
                    early_exaggeration=10.0)
  X_TSNE = TSNE_model.fit_transform(X_train_cut)

  KernelPCA_model = KernelPCA(n_components=2)
  KernelPCA_model.fit(X_train_cut)
  X_KernelPCA_test = KernelPCA_model.transform(X_test_cut)
  X_KernelPCA_train = KernelPCA_model.transform(X_train_cut)

  Isomap_model = Isomap(n_neighbors=15)
  Isomap_model.fit(X_train_cut)
  X_Isomap_test = Isomap_model.transform(X_test_cut)
  X_Isomap_train = Isomap_model.transform(X_train_cut)

  fig, ax = plt.subplots(2, 4, figsize=(10, 5))
  fig.tight_layout()
  plot_2d_data(X_TSNE, lab_train, ax =ax[0, 0], title="TSNE")

  plot_2d_data(X_UMAP_test, lab_test, ax =ax[0, 1], title="UMAP")
  plot_2d_data(X_UMAP_train, lab_train, ax =ax[1, 1], title="UMAP")

  plot_2d_data(X_KernelPCA_test, lab_test, ax =ax[0, 2], title="KernelPCA")
  plot_2d_data(X_KernelPCA_train, lab_train, ax =ax[1, 2], title="KernelPCA")

  plot_2d_data(X_Isomap_test, lab_test, ax =ax[0, 3], title="Isomap")
  plot_2d_data(X_Isomap_train, lab_train, ax =ax[1, 3], title="Isomap")
In [157]:
plot_grid(cifar10_data_train[:2000] ,cifar10_labels_train[:2000],  cifar10_data_test[:2000], cifar10_labels_test[:2000]);

**Задание 2.2 [кросспроверка, 3 балла][вопрос]**¶

Опишите увиденное. Почему алгоритмы могли отработать не так, как вы ожидали?

*Ваш ответ здесь:* (o・・)ノ”(ノ<、) Алгоритмы, хоть и понизили размерности, но плохо выделили класстеры. Это могло произойти из-за того, что данные невозможно проецировать на двухразмерную плоскость из-за их природы, либо же просто в исходных класстерах объекты находятся вразброс.

**Задание 2.3 [кросспроверка, 2 балла][вопрос]**¶

Методы снижения размерности, как и другие метрические методы испытывают трудности при работе с данными высокой размерности. Напишите как минимум две причины, почему.

*Ваш ответ здесь:*(o・・)ノ”(ノ<、)

  • Требуют много памяти
  • Требуют много времени для вычислений

Один из способов решения этих проблем — перейти в другое, более репрезентативное пространство признаков, где объекты будут расположены в многообразии, которое легче представить в двумерном пространстве. Чтобы выполнить такое преобразование воспользуемся типичным подходом Transfer Learning — предобученными нейронными сетями. С помощью глубокой сети обученной на другом наборе изображений (ImageNet) мы перейдём в новое векторное пространство и затем применим методы снижения размерности.

Так как локальный подсчёт эмбеддингов изображений может занять много времени, Вы можете попробовать скачать их из проверяющей системы, или через gdown:

In [ ]:
gdown.download(id='16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-', output='cifar10_deep_features.npy')
Downloading...
From (original): https://drive.google.com/uc?id=16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-
From (redirected): https://drive.google.com/uc?id=16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-&confirm=t&uuid=c95dabb0-5487-4a4e-9d6e-5485b0398350
To: /content/cifar10_deep_features.npy
100%|██████████| 164M/164M [00:01<00:00, 136MB/s]
Out[ ]:
'cifar10_deep_features.npy'
In [ ]:
FEATURES_PATH = './cifar10_deep_features.npy'

if not os.path.exists(FEATURES_PATH):
    deep_cnn = InceptionV3(weights='imagenet', include_top=False, input_shape=(139, 139, 3))

    cifar10_tensors_test = torch.nn.functional.interpolate(torch.tensor(
        cifar10_images_test.transpose(0, 3, 1, 2)
    ), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)
    cifar10_tensors_train = torch.nn.functional.interpolate(torch.tensor(
        cifar10_images_train.transpose(0, 3, 1, 2)
    ), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)

    cifar10_deep_features_test = deep_cnn.predict(
        preprocess_input(cifar10_tensors_test)
    ).mean(axis=(1, 2)).reshape([cifar10_tensors_test.shape[0], -1])
    cifar10_deep_features_train = deep_cnn.predict(
        preprocess_input(cifar10_tensors_train)
    ).mean(axis=(1, 2)).reshape([cifar10_tensors_train.shape[0], -1])

    np.save(FEATURES_PATH, [cifar10_deep_features_test, cifar10_deep_features_train])
else:
    cifar10_deep_features_test, cifar10_deep_features_train = np.load(FEATURES_PATH, allow_pickle=True)

**Задание 2.4 [кросспроверка, 4 балла][код]**¶

Используйте выделенные признаки для обучения алгоритмов из предыдущего пункта. Постройте графики. Замечание из пункта 2.1 остаётся в силе.

In [ ]:
# Ваш код здесь:\(º □ º l|l)/
X_train = StandardScaler().fit_transform(cifar10_deep_features_train)
X_test = StandardScaler().fit_transform(cifar10_deep_features_test)

plot_grid(X_train, cifar10_labels_train, X_test, cifar10_labels_test)

**Задание 2.5 [кросспроверка, 3 балла][вопрос]**¶

  1. Есть ли какие-то изменения по сравнению с использованием исходных признаков?
  2. Как вы думаете, почему использование глубоких признаков помогло/не помогло в задаче снижения размерности?
  3. Какой алгоритм показал себя лучше на ваш взгляд?
  4. Согласованы ли преобразования на обучающей и тестовых выборках? Какие недостатки есть в том, что преобразование на тестовой выборке выглядит отлично от низкоразмерного представления обучающей выборки?
  5. Какие из алгоритмов можно использовать в качестве первого шага по снижению размерности в задачах машинного обучения? Какой из них использовали бы вы?

*Ваш ответ здесь:* (o・・)ノ”(ノ<、)

  1. После использования эмбеддингов при понижении размерности классетры разделились лучше по сравнению с исходными данными.
  2. Глубокие признаки помогли потому что они показывают какие-то информативные признаки.
  3. UMAP справился лучше остальных.
  4. Преобразования на обучающей и тестовых выборках более менее похожи. В случае когда предсказание на тестовой выборке выглядит иначечем низкоразмерное представления обучающей выборки, то это значит что метод переобучился, либо же недообучился.
  5. Все представленные алгоритмы вполне можно использовать в качестве первого шага по снижению размерности в задачах машинного обучения. На мой взгляд стоит использовать метод UMAP, так как он быстро обучается и показывает лучшие результаты.

Далее, для визуализации кластеризации используйте один из методов снижения размерности на ваш выбор и то векторное представление, которое лучше всего себя проявило (исходное или полученное с помощью глубокой сети). Кластеризацию обучайте также на наиболее подходящем высокоразмерном векторном представлении.

**Задание 2.6 [кросспроверка, 2 балла][код, вопрос]**¶

Изобразите выборку CIFAR10 с помощью выбранного алгоритма снижения размерности.

Совет: Изобразите результат с помощью plot_interactive, чтобы изучить особенности кластеризации в соответствии с исходными изображениями. Если вы нашли интересные особенности — напишите про это.

In [ ]:
%matplotlib ipympl
matplotlib.rcParams['figure.dpi'] = 300

# Для работы в Google Colab нужно выполнить специфичную магию
# Обычно, она не срабатывает с первого раза, поэтому может потребоваться
#   несколько раз выполнить ячейку и несколько раз попробовать нарисовать график
try:
    from google.colab import output
    output.enable_custom_widget_manager()
except:
    pass
In [ ]:
# Ваш код здесь:\(º □ º l|l)/
cifar10_deep_features_train_scaled = StandardScaler().fit_transform(cifar10_deep_features_train)
umap = UMAP().fit(cifar10_deep_features_train_scaled)

plot_interactive(
    umap.transform(cifar10_deep_features_train_scaled), cifar10_images_train, cifar10_labels_train,
    cifar10_test_dataset.classes, n_dots=2000, image_scale=0.35
)
Figure

Вернёмся в статичный режим отрисовки изображений:

In [ ]:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 100
plt.style.use("ggplot")

Теперь, когда мы можем визуализировать кластеризацию, можно сравнить алгоритмы из первой части на естественных данных.

**Задание 2.7 [кросспроверка, 3 балла][код]**¶

Подберите параметры KMeans, DBSCAN, AgglomerativeClustering используя силуэт и B-Cubed. Визуализируйте получившиеся кластеризации также, как и в задании 1.с.4. Для ускорения перебора можете производить его на небольшой доле от всех объектов ($1000\text{-}2000$ объектов).

Замечание: Алгоритмы кластеризации нужно применять к исходному векторному представлению. Снижение размерности используется только для визуализации.

In [ ]:
n_objects = 2000
In [ ]:
UMAP_model = UMAP(n_neighbors=15, n_components=2, min_dist=0.0)
UMAP_model.fit(X_train[:n_objects])
X_UMAP_train = UMAP_model.transform(X_train[:n_objects])
In [ ]:
KMeans_metrics(X_train[:n_objects], X_UMAP_train, cifar10_labels_train[:n_objects])
In [ ]:
DBSCAN_metrics(X_train[:n_objects], X_UMAP_train, cifar10_labels_train[:n_objects])
In [158]:
AgglClust_metrics(X_train[:n_objects], X_UMAP_train, cifar10_labels_train[:n_objects])

**Задание 2.8 [кросспроверка, 3 балла][вопрос]** ¶

  1. Какие алгоритмы справились с кластеризацией естественных данных?
  2. Получилось ли подобрать оптимальное число кластеров с помощью BCubed и коэффициента силуэта?
  3. Объясните почему коэффициент силуэта не позволил выполнить подбор оптимальных гиперпараметров.

*Ваш ответ здесь:* (o・・)ノ”(ノ<、)

  1. С класстеризацией естественных данных справились только KMeans и AgglomerativeClustering.
  2. По BCubed удалось подобрать оптимальные параметры, а по коэффициенту силуэта нет.
  3. Коэффициент силуэта не помог подобрать оптимальные гиперпаметры потому что это внутренняя метрика и при этом используется евклидова мера, но при работе в пространстве высокой размерности метрические алгоритмы работают плохо.

Интересный способ визуализации Иерархической кластеризации — построение дендрограммы. Такой способ визуализации позволяет анализировать, как именно связаны между собой объекты, подбирать оптимальное число кластеров, а также определять, какие классы отделяются "хорошо" от других классов, а какие классы перемешаны в одном кластере.

In [177]:
def plot_dendrogram(model, labels, classes, ax):
    n_classes = len(classes)
    n_samples = len(model.labels_)
    n_u_connections = model.children_.shape[0]
    colors = plt.get_cmap('tab20', n_classes).colors

    bin_counts = np.zeros([n_u_connections, n_classes])
    for i, merge in enumerate(model.children_):
        current_bin_count = np.zeros(n_classes)
        for child_idx in merge:
            if child_idx < n_samples:
                current_bin_count[labels[child_idx]] += 1
            else:
                current_bin_count += bin_counts[child_idx - n_samples]

        bin_counts[i] = current_bin_count

    linkage_matrix = np.column_stack(
        [model.children_, model.distances_, np.sum(bin_counts, axis=1)]
    ).astype(float)

    def leaf_label_func(idx):
        if idx < len(labels):
            return None
        else:
            ratio = 100 * np.max(bin_counts[idx - n_samples]) / np.sum(bin_counts[idx - n_samples])
            if ratio < 100:
                return '{0:.0f}%'.format(ratio)
            else:
                return None

    def link_color_func(idx):
        mode_class = np.argmax(bin_counts[idx - n_samples])
        return matplotlib.colors.to_hex(colors[mode_class], keep_alpha=True)

    scipy.cluster.hierarchy.dendrogram(
        linkage_matrix, ax=ax, link_color_func=link_color_func, leaf_label_func=leaf_label_func,
        orientation='right', truncate_mode="level", p=9
    )

    for idx, class_name in enumerate(classes):
        ax.plot([], [], c=matplotlib.colors.to_hex(colors[idx], keep_alpha=True), label=class_name)
    ax.legend()

    # Удалим накладывающиеся метки
    threshold = 55
    prev_position = -(threshold + 1)

    y_labels = ax.get_yaxis().get_ticklabels()
    for label in y_labels:
        if label.get_text() == '':
            continue

        _, position = label.get_position()
        if position - prev_position < threshold:
            label.set_text('')
        else:
            prev_position = position
    ax.get_yaxis().set_ticklabels(y_labels)

    ax.set_xlabel('Расстояние между кластерами')
    ax.set_ylabel('Доля объектов наибольшего класса в данном кластере')

    ax.set_title('Дендрограмма Иерархической Кластеризации')
In [179]:
n_objects = 2000
model = AgglomerativeClustering(
    n_clusters=None, distance_threshold=0.0, compute_distances=True, compute_full_tree=True
)
model = model.fit(cifar10_deep_features_train[:n_objects])

fig, ax = plt.subplots(1, 1, figsize=(12, 12))

plot_dendrogram(
    model,
    labels=cifar10_labels_train[:n_objects],
    classes=cifar10_train_dataset.classes,
    ax=ax
)

fig.tight_layout()
plt.show()

**Задание 2.9 [вопрос]** ¶

Проанализируйте получившуюся дендрограмму. Напишите свои наблюдения ниже.

*Ваш ответ здесь:* (o・・)ノ”(ノ<、) Ваш ответ здесь:

Дендорограмма похожа на турнирную таблицу, на следующий уровень прохолит тот класс, представителей которого больше всего на предыдущем разбиении.